﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

namespace Framework.Graphics.Light
{
    enum ShadowmapSize
    {
        Size128 = 6,
        Size256 = 7,
        Size512 = 8,
        Size1024 = 9,
        Size2048 = 10,
    }
    class ShadowmapResolver
    {
        private GraphicsDevice _graphicsDevice;

        private int _reductionChainCount;
        private int _baseSize;
        private int _depthBufferSize;

        Effect _resolveShadowsEffect;
        Effect _reductionEffect;

        RenderTarget2D _distortRT;
        RenderTarget2D _shadowMap;
        RenderTarget2D _shadowsRT;
        RenderTarget2D _processedShadowsRT;

        QuadRenderComponent _quadRender;
        RenderTarget2D _distancesRT;
        RenderTarget2D[] _reductionRT;

        public ShadowmapResolver(GraphicsDevice graphicsDevice, QuadRenderComponent quadRender, ShadowmapSize maxShadowmapSize, ShadowmapSize maxDepthBufferSize)
        {
            this._graphicsDevice = graphicsDevice;
            this._quadRender = quadRender;

            _reductionChainCount = (int)maxShadowmapSize;
            _baseSize = 2 << _reductionChainCount;
            _depthBufferSize = 2 << (int)maxDepthBufferSize;
        }

        public void LoadContent(ContentManager content)
        {
            _reductionEffect = content.Load<Effect>("reductionEffect");
            _resolveShadowsEffect = content.Load<Effect>("resolveShadowsEffect");

            _distortRT = new RenderTarget2D(_graphicsDevice, _baseSize, _baseSize, false, SurfaceFormat.HalfVector2, DepthFormat.None);
            _distancesRT = new RenderTarget2D(_graphicsDevice, _baseSize, _baseSize, false, SurfaceFormat.HalfVector2, DepthFormat.None);
            _shadowMap = new RenderTarget2D(_graphicsDevice, 2, _baseSize, false, SurfaceFormat.HalfVector2, DepthFormat.None);
            _reductionRT = new RenderTarget2D[_reductionChainCount];
            for (int i = 0; i < _reductionChainCount; i++)
            {
                _reductionRT[i] = new RenderTarget2D(_graphicsDevice, 2 << i, _baseSize, false, SurfaceFormat.HalfVector2, DepthFormat.None);
            }


            _shadowsRT = new RenderTarget2D(_graphicsDevice, _baseSize, _baseSize);
            _processedShadowsRT = new RenderTarget2D(_graphicsDevice, _baseSize, _baseSize);
        }

        public void ResolveShadows(Texture2D shadowCastersTexture, RenderTarget2D result, Vector2 lightPosition)
        {
            _graphicsDevice.BlendState = BlendState.Opaque;

            ExecuteTechnique(shadowCastersTexture, _distancesRT, "ComputeDistances");
            ExecuteTechnique(_distancesRT, _distortRT, "Distort");
            ApplyHorizontalReduction(_distortRT, _shadowMap);
            ExecuteTechnique(null, _shadowsRT, "DrawShadows", _shadowMap);
            ExecuteTechnique(_shadowsRT, _processedShadowsRT, "BlurHorizontally");
            ExecuteTechnique(_processedShadowsRT, result, "BlurVerticallyAndAttenuate");
        }

        private void ExecuteTechnique(Texture2D source, RenderTarget2D destination, string techniqueName)
        {
            ExecuteTechnique(source, destination, techniqueName, null);
        }

        private void ExecuteTechnique(Texture2D source, RenderTarget2D destination, string techniqueName, Texture2D shadowMap)
        {
            Vector2 renderTargetSize;
            renderTargetSize = new Vector2((float)_baseSize, (float)_baseSize);
            _graphicsDevice.SetRenderTarget(destination);
            _graphicsDevice.Clear(Color.White);
            _resolveShadowsEffect.Parameters["renderTargetSize"].SetValue(renderTargetSize);

            if (source != null)
                _resolveShadowsEffect.Parameters["InputTexture"].SetValue(source);
            if (shadowMap != null)
                _resolveShadowsEffect.Parameters["ShadowMapTexture"].SetValue(shadowMap);

            _resolveShadowsEffect.CurrentTechnique = _resolveShadowsEffect.Techniques[techniqueName];

            foreach (EffectPass pass in _resolveShadowsEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                _quadRender.Render(Vector2.One * -1, Vector2.One);
            }
            _graphicsDevice.SetRenderTarget(null);
        }


        private void ApplyHorizontalReduction(RenderTarget2D source, RenderTarget2D destination)
        {
            int step = _reductionChainCount - 1;
            RenderTarget2D s = source;
            RenderTarget2D d = _reductionRT[step];
            _reductionEffect.CurrentTechnique = _reductionEffect.Techniques["HorizontalReduction"];

            while (step >= 0)
            {
                d = _reductionRT[step];

                _graphicsDevice.SetRenderTarget(d);
                _graphicsDevice.Clear(Color.White);

                _reductionEffect.Parameters["SourceTexture"].SetValue(s);
                Vector2 textureDim = new Vector2(1.0f / (float)s.Width, 1.0f / (float)s.Height);
                _reductionEffect.Parameters["TextureDimensions"].SetValue(textureDim);

                foreach (EffectPass pass in _reductionEffect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    _quadRender.Render(Vector2.One * -1, new Vector2(1, 1));
                }

                _graphicsDevice.SetRenderTarget(null);

                s = d;
                step--;
            }

            //copy to destination
            _graphicsDevice.SetRenderTarget(destination);
            _reductionEffect.CurrentTechnique = _reductionEffect.Techniques["Copy"];
            _reductionEffect.Parameters["SourceTexture"].SetValue(d);

            foreach (EffectPass pass in _reductionEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                _quadRender.Render(Vector2.One * -1, new Vector2(1, 1));
            }

            _reductionEffect.Parameters["SourceTexture"].SetValue(_reductionRT[_reductionChainCount - 1]);
            _graphicsDevice.SetRenderTarget(null);
        }


    }
}
